use glam::{dvec3, DVec3};


pub const SCREEN_W: usize = 1280;
pub const SCREEN_H: usize = 720;
pub const F64_SCREEN_W: f64 = SCREEN_W as f64;
pub const SCREEN_PIXEL_NUM: usize = SCREEN_W * SCREEN_H;

/// convert degree to radains
/// 
pub fn degrees_to_radians(degrees: f64) -> f64 {
    degrees * std::f64::consts::PI / 180.0
}

/// random [0,1) in standard uniform distribution
/// 
#[allow(unused)]
pub fn random_standard_uniform() -> f64 {
    use rand::prelude::*;
    use rand::distr::StandardUniform;

    rand::rng().sample(StandardUniform)
}

#[allow(unused)]
pub fn random_range(min: f64, max: f64) -> f64 {
    min + (max - min) * random_standard_uniform()
}

/// random [min, max) in standard uniform distribution
/// 
#[inline]
#[allow(unused)]
pub fn random_range_usize<T>(
    min: T,
    max: T,
) -> T
where T: num_traits::NumCast
{
    let f64_min = min.to_f64().unwrap();
    let f64_max = max.to_f64().unwrap();
    let value = (f64_min + (f64_max - f64_min)) * random_standard_uniform();
    T::from(value).unwrap()
}

#[allow(unused)]
pub fn random_vec3_range(min: f64, max: f64) -> DVec3 {
    DVec3::new(
        random_range(min, max),
        random_range(min, max),
        random_range(min, max),
    )
}

#[allow(unused)]
pub fn random_unit_vec()-> DVec3 {
    loop {
        let p = random_vec3_range(-1.0, 1.0);
        let l = p.length_squared();

        if 1e-160 < l && l <= 1.0 {
            return p.normalize();
        }
    }
}

#[allow(unused)]
pub fn random_on_hemisphere(normal: &DVec3) -> DVec3 {
    let on_unit_sphere = random_unit_vec();
    if on_unit_sphere.dot(*normal) > 0.0 {
        on_unit_sphere
    } else {
        -on_unit_sphere
    }
}

#[inline]
pub fn random_uint_disk() -> DVec3 {
    loop {
        let p = dvec3(
            random_range(-1.0, 1.0), 
            random_range(-1.0, 1.0), 
            0.0
        );
        
        if p.length_squared() < 1.0 {
            return p;
        }
    }
}


#[inline]
pub fn reflect(v: &DVec3, n: &DVec3) -> DVec3 {
    v - 2.0 * v.dot(*n) * n
}

#[inline]
pub fn refract(uv: &DVec3, n: &DVec3, etai_over_etat: f64) -> DVec3 {
    let cos_theta = (-uv).dot(*n).min(1.0);
    let r_out_perp = etai_over_etat * (uv + cos_theta * n);
    let r_out_parallel = -((1.0 - r_out_perp.length_squared()).abs().sqrt()) * n;
    r_out_perp + r_out_parallel
}


/// is vector near zero
///
#[inline]
#[allow(unused)]
pub fn is_near_zero(v: &DVec3) -> bool {
    v.x.abs() < f64::EPSILON &&
    v.y.abs() < f64::EPSILON &&
    v.z.abs() < f64::EPSILON
}


/// convert linear color to gamma color, in gamma 2.2 space
///
#[inline]
pub fn linear_to_gamma(linear: &mut DVec3) {
    linear.x = linear.x.powf(1.0 / 2.2);
    linear.y = linear.y.powf(1.0 / 2.2);
    linear.z = linear.z.powf(1.0 / 2.2);
}

#[derive(Default, Clone, Copy)]
pub struct Interval {
    pub min: f64,
    pub max: f64,
}

impl Interval {
    pub const fn new(min: f64, max: f64) -> Self {
        Self {
            min: min.min(max),
            max: min.max(max),
        }
    }

    pub const fn from_two_interval(a: &Self, b: &Self) -> Self {
        Self {
            min: a.min.min(b.min),
            max: a.max.max(b.max),
        }
    }

    pub fn clamp(&self, x: f64) -> f64 {
        x.clamp(self.min, self.max)
    }

    pub fn size(&self) -> f64 {
        self.max - self.min
    }

    pub fn contains(&self, x: f64) -> bool {
        self.min <= x && x <= self.max
    }

    /// expand interval with delta
    /// 
    pub fn expand(&mut self, delta: f64) {
        let padding = delta / 2.0;
        self.min -= padding;
        self.max += padding;
    }
}

/// add f64 to interval
/// 
impl std::ops::Add<f64> for Interval {
    type Output = Self;
    fn add(self, rhs: f64) -> Self::Output {
        Self {
            min: self.min + rhs,
            max: self.max + rhs,
        }
    }
}

pub const INTERVAL: Interval = Interval::new(0.000, 0.999);
